package sativa.game;

import org.lwjgl.util.glu.GLU;
import org.lwjgl.opengl.GL11;
import org.lwjgl.input.Keyboard;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import planets.DrawPanel;

/**
 * Sets up the window and all the GL stuff. Subclass this to code the game update and draw logic.
 * 
 * @author Evan Tatarka
 */
public abstract class Engine
{
	private long timerTicksPerSecond;

	private boolean gameRunning;

	/**
	 * The number of frames per second. This defaults to 60.
	 */
	public static final int FPS = 60;

	private int width;
	private int height;

	/**
	 * Construct our game and set it running.
	 * 
	 * @param width
	 *            the width of the game
	 * @param height
	 *            the height of the game
	 * @param drawPanel
	 *            the drawPanel to draw the game on in windowed mode.
	 */
	public Engine(int width, int height, DrawPanel drawPanel)
	{
		this.width = width;
		this.height = height;

		gameRunning = false;
		timerTicksPerSecond = Sys.getTimerResolution();

		try
		{
			// Tell LWJGL to draw in our drawPanel's canvas
			Display.setParent(drawPanel.getCanvas());
		} catch (LWJGLException e) // If something prevents the display from being created
		{
			e.printStackTrace();
		}

		initialize();
	}

	/**
	 * Get the high resolution time in milliseconds
	 * 
	 * @return The high resolution time in milliseconds
	 */
	private long getTime()
	{
		// we get the "timer ticks" from the high resolution timer
		// multiply by 1000 so our end result is in milliseconds
		// then divide by the number of ticks in a second giving
		// us a nice clear time in milliseconds
		return (Sys.getTime() * 1000) / timerTicksPerSecond;
	}

	/**
	 * Initialize the common elements for the game
	 */
	private void initialize()
	{
		// initialize the window beforehand
		try
		{
			Display.create();
			Display.setDisplayMode(new DisplayMode(width, height));
		} catch (LWJGLException e)
		{
			e.printStackTrace();
		}

		Keyboard.enableRepeatEvents(true);

		// enable textures since we're going to use these for our sprites
		// GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_CULL_FACE);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glDepthFunc(GL11.GL_LEQUAL);
		GL11.glShadeModel(GL11.GL_SMOOTH);

		GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GLU.gluPerspective(90.0f, width / height, 0.1f, 100.0f);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
	}

	/**
	 * Sleep for a fixed number of milliseconds.
	 * 
	 * @param duration
	 *            The amount of time in milliseconds to sleep for
	 */
	private void sleep(long duration)
	{
		try
		{
			Thread.sleep((duration * timerTicksPerSecond) / 1000);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Run the main game loop. This method keeps rendering the scene and requesting that the callback update its screen.
	 */
	private void gameLoop()
	{
		while (gameRunning)
		{
			// clear screen
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
			GL11.glMatrixMode(GL11.GL_MODELVIEW);
			GL11.glLoadIdentity();

			long preRoutineTime = getTime();

			// run game logic
			update(preRoutineTime);

			// let subsystem paint
			draw();

			// update window contents
			Display.update();

			// stabilize the FPS
			keepTime(preRoutineTime);
		}

		// clean up
		Display.destroy();
	}

	/**
	 * Stabilize the FPS
	 * 
	 * @param currentTime
	 *            The time before calling game routines
	 */
	private void keepTime(long preRoutineTime)
	{
		long elapsedTime = getTime() - preRoutineTime;
		long targetTime = (long) (1000 * (1 / (double) FPS));

		// Sleeps to keep in time with the FPS
		if (elapsedTime < targetTime)
		{
			sleep(targetTime - elapsedTime);
		}
	}

	// ----------------------------------------------------------
	/**
	 * Loads all the assests for the game and initializes everything.
	 */
	public abstract void load();

	// ----------------------------------------------------------
	/**
	 * Updates the game loop. This at a rate based on the FPS.
	 * 
	 * @param gameTime
	 */
	public abstract void update(long gameTime);

	// ----------------------------------------------------------
	/**
	 * Draws the game.
	 */
	public abstract void draw();

	/**
	 * Starts the game for the first time
	 */
	public void execute()
	{
		load();
		gameRunning = true;
		gameLoop();
	}

	// ----------------------------------------------------------
	/**
	 * Starts or stops the game
	 * 
	 * @param isRunning
	 *            Whether you want the game to run or stop
	 */
	public void setRunning(boolean isRunning)
	{
		gameRunning = isRunning;
	}

	// ----------------------------------------------------------
	/**
	 * Determines if the game is running.
	 * 
	 * @return true if it is, false otherwises
	 */
	public boolean isRunning()
	{
		return gameRunning;
	}
}